#pragma once

#include <iostream>
#include <string>
#include <functional>

#include <cstring>
#include <cstdlib>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "log.hpp"

extern Log log;

constexpr int default_sockfd = -1;
constexpr uint16_t default_port = 8080;
const std::string default_ip = "0.0.0.0";
constexpr int RECV_SIZE = 1024;

class UDPServer
{
public:
    typedef std::function<void(int, struct sockaddr_in, const std::string&, const std::string&, uint16_t)> func_t;

    enum UDPServer_Error
    {
        SOCKET_CREATE_ERROR = 1,
        SOCKET_BIND_ERROR
    };

    UDPServer(uint16_t port = default_port, const std::string& ip = default_ip)
        : _sockfd(default_sockfd)
        , _port(port)
        , _ip(ip)
        , _is_running(false)
    {}

    void server_init()
    {
        //创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            log(Fatal, "socket create error, sockfd = %d, errno: %d, errstring: %s", _sockfd, errno, strerror(errno));
            exit(SOCKET_CREATE_ERROR);
        }
        log(Info, "socket create success, sockfd = %d", _sockfd);
        
        //绑定套接字
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);                    //端口号是要被发送出去的,需转为网络字节流格式
        local.sin_addr.s_addr = inet_addr(_ip.c_str());   //需要将IP转为网络字节序uint32_t类型
        // local.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(_sockfd, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            log(Fatal, "socket bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SOCKET_BIND_ERROR);
        }
        log(Info, "socket bind success");
    }

    void run(func_t func)
    {
        _is_running = true;
        char inbuffer[RECV_SIZE];
        while (_is_running)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_sockfd, inbuffer, RECV_SIZE - 1, 0, (struct sockaddr*)&client, &len);
            if (n < 0)
            {
                log(Warning, "recvfrom error, errno: %d, errstring: %s", errno, strerror(errno));
                continue;
            }

            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);
            inbuffer[n] = 0;
            std::string info = inbuffer;

            func(_sockfd, client, info, clientip, clientport);
        }
    }

    ~UDPServer()
    {
        if (_sockfd > 0)
            close(_sockfd);
    }
private:
    int _sockfd;    //网络文件描述符
    uint16_t _port; //端口号
    std::string _ip;   //ip
    bool _is_running;
};